#pragma once
#include <cstdint>
#include <log.h>
#include <memory>
#include <odb/mysql/database.hxx>
#include <odb/result.hxx>
#include <vector>

#include "Problem.hpp"
#include "Problem-odb.hxx"
#include "problem.pb.h"

// 本单元开始不使用rollback，如果事务没有提交就会自动进行rollback

namespace lyt {
    class ProblemData{
    private:
        std::shared_ptr<odb::mysql::database> db;
        // 这里的模板也可以改为std::exception& e，odb::exception& e作为形参照样可以被函数接收
        template<class T>
        bool log_odb_exception(const T& e,const std::string& message = ""){
            static_assert(std::is_base_of_v<std::exception, T>, "T must derive from std::exception");
            errorlog << message << ": " << e.what();
            return false;
        }
    public:
        using ptr = std::shared_ptr<ProblemData>;
        ProblemData(){}
        ProblemData(const std::shared_ptr<odb::mysql::database>& client):db(client){}
    public:
        // ====================================Problem表===========================================
        bool insert_problem(Problem& problem){
            try{
                odb::transaction transa(db->begin());
                db->persist(problem);
                transa.commit();
                return true;
            }catch (const odb::exception& e) {
                return log_odb_exception(e,"ODB异常");
            } catch (const std::exception& e) {
                return log_odb_exception(e,"其他异常");
            }
        }
        // 按照问题id查找
        bool query_problem(const std::uint64_t& id,Problem& problem){
            try{
                odb::transaction transa(db->begin());
                // db->load()返回是一个unique_ptr
                problem = *db->load<Problem>(id);
                transa.commit();
                return true;
            }catch(const odb::object_not_persistent& e){
                return log_odb_exception(e,"其他异常");

            }catch (const odb::exception& e) {
                return log_odb_exception(e,"ODB异常");

            } catch (const std::exception& e) {
                return log_odb_exception(e,"其他异常");
            }
        }
        bool update_problem(const Problem& problem) {
            try {
                odb::transaction transa(db->begin());
                // 更新对象到数据库
                db->update(problem);
                transa.commit();
                return true;
            } catch (const odb::exception& e) {
                return log_odb_exception(e,"ODB异常");

            } catch (const std::exception& e) {
                return log_odb_exception(e,"其他异常");
            }
        }
        bool delete_blog(const std::uint64_t& id){
            odb::transaction transa;
            try{
                transa.reset(db->begin());
                using query = odb::query<Problem>;
                db->erase_query<Problem>(query::problem_id == id);
                transa.commit();
                return true;  
            } catch (const odb::exception& e) {
                return log_odb_exception(e,"ODB异常");

            } catch (const std::exception& e) {
                return log_odb_exception(e,"其他异常");
            }
        }
        // ====================================Problem表 end===========================================

        // ====================================TestCases表===========================================

        bool insert_case(ProblemCases& testcase){
            try{
                odb::transaction transa(db->begin());
                db->persist(testcase);
                transa.commit();
                return true;
            }catch (const odb::exception& e) {
                return log_odb_exception(e,"ODB异常");

            } catch (const std::exception& e) {
                return log_odb_exception(e,"其他异常");
            }
        }

        bool query_cases(const std::uint32_t problem_id
                        ,std::vector<problem::TestCase>& testcases){
            try{
                odb::transaction transa(db->begin());
                using query = odb::query<ProblemCases>;
                auto result = db->query<ProblemCases>(query::problem_id == problem_id); 
                testcases.reserve(result.size());
                for(auto& e:result){
                    problem::TestCase testcase;
                    testcase.set_input(e.input);
                    testcase.set_output(e.output);
                    testcase.set_explanation(e.explanation);
                    testcases.push_back(testcase);
                }
                transa.commit();
                return true;
            }catch (const odb::exception& e) {
                return log_odb_exception(e,"ODB异常");

            } catch (const std::exception& e) {
                return log_odb_exception(e,"其他异常");
            }
        }
        bool delete_cases(const std::uint64_t& id){
            try{
                odb::transaction transa(db->begin());
                using query = odb::query<ProblemCases>;
                db->erase_query<ProblemCases>(query::problem_id == id); 
                transa.commit();
                return true;
            }catch (const odb::exception& e) {
                return log_odb_exception(e,"ODB异常");

            } catch (const std::exception& e) {
                return log_odb_exception(e,"其他异常");
            }
        }

        // =========================================submit record表==============================
        bool insert_submit_record(ProblemSubmissionRecord& subrecord){
            try{
                odb::transaction transa(db->begin());
                db->persist(subrecord);
                transa.commit();
                return true;
            }catch (const odb::exception& e) {
                return log_odb_exception(e,"ODB异常");

            } catch (const std::exception& e) {
                return log_odb_exception(e,"其他异常");
            }
        }
        bool query_submit_records(const std::string& user_id
                                ,const uint32_t problem_id
                                ,const uint32_t page
                                ,const uint32_t page_size
                                ,std::vector<problem::SubmitResult>& results){
            try{
                odb::transaction transa(db->begin());
                using query = odb::query<ProblemSubmissionRecord>;
                using result = odb::result<ProblemSubmissionRecord>;
                auto condition = (query::user_id == user_id 
                                            && query::problem_id == problem_id);
                int offset = (page - 1) * page_size;
                result res = db->query<ProblemSubmissionRecord>(condition + " ORDER BY id DESC" + " LIMIT " 
                            + std::to_string(page_size) + " OFFSET " + std::to_string(offset));
                // db->query_value<std::size_t>(query::user_id == user_id && query::problem_id == problem_id);
                for (const auto& e : res) {
                    problem::SubmitResult sr;
                    sr.set_id(e.submit_id);
                    sr.set_accepted(e.accept);
                    sr.set_language(e.language);
                    sr.set_time(e.time);
                    results.emplace_back(std::move(sr));
                }
                transa.commit();
                return true;
            }catch (const odb::exception& e) {
                return log_odb_exception(e,"ODB异常");

            } catch (const std::exception& e) {
                return log_odb_exception(e,"其他异常");
            }
        }
        bool query_submit_record(const std::string& submit_id
                                ,ProblemSubmissionRecord& record){
            try{
                odb::transaction transa(db->begin());
                using query = odb::query<ProblemSubmissionRecord>;
                using result = odb::result<ProblemSubmissionRecord>;
                result res = db->query<ProblemSubmissionRecord>(query::submit_id == submit_id);
                auto ptr = res.begin();
                record.submit_id = ptr->submit_id;
                record.accept = ptr->accept;
                record.language = ptr->language;
                record.time = ptr->time;
                transa.commit();
                return true;
            }catch (const odb::exception& e) {
                return log_odb_exception(e,"ODB异常");

            } catch (const std::exception& e) {
                return log_odb_exception(e,"其他异常");
            }
        }
    };
}